package botcraft.game;

import java.awt.Image;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.imageio.ImageIO;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;

import botcraft.game.unit.Building;
import botcraft.game.unit.MobileUnit;
import botcraft.game.unit.Unit;

public class ObjectManager {
	private static final Logger LOGGER = Logger.getLogger(ObjectManager.class.getName());
	private static final int IMAGE_WIDTH = 64;
	private static final int IMAGE_HEIGHT = 64;

	private final Map<String, Constructor<? extends Object>> constructors;
	private final Map<String, Icon> icons;
	private final Map<String, ResourceBundle> resourceBundles;
	private final Map<String, Set<JButton>> actions;
	private final Set<String> buildings;
	private final Set<String> mobileUnits;
	
	public ObjectManager() {
		constructors = new HashMap<String, Constructor<? extends Object>>();
		icons = new HashMap<String, Icon>();
		resourceBundles = new HashMap<String, ResourceBundle>();
		actions = new HashMap<String, Set<JButton>>();
		buildings = new HashSet<String>();
		mobileUnits = new HashSet<String>();
	}
	
	public void loadObjectPackage(final URL objectPackage) {
		for (Object object : ServiceLoader.load(Object.class, URLClassLoader.newInstance(new URL[] {objectPackage}, ObjectManager.class.getClassLoader()))) {
			try {
				final Constructor<? extends Object> constructor = object.getClass().getDeclaredConstructor(UUID.class, Vector.class, Vector.class);
				constructor.setAccessible(true);
				final botcraft.game.annotations.Object objectAnnotation = object.getClass().getAnnotation(botcraft.game.annotations.Object.class);
				if (objectAnnotation == null) {
					LOGGER.warning("Error loading object package " + objectPackage + ": object class " + object.getClass().getName() + " has no " + botcraft.game.annotations.Object.class.getName() + " annotation");
					return;
				}
				final URL iconUrl = object.getClass().getClassLoader().getResource(objectAnnotation.icon()); 
				if (iconUrl == null) {
					LOGGER.warning("Error loading object package " + objectPackage + ": object icon " + objectAnnotation.icon() + " does not exist");
					return;
				}
				final Icon icon = new ImageIcon(ImageIO.read(iconUrl).getScaledInstance(IMAGE_WIDTH, IMAGE_HEIGHT, Image.SCALE_SMOOTH));
				final ResourceBundle resourceBundle = ResourceBundle.getBundle(objectAnnotation.resourceBundle(), Locale.getDefault(), object.getClass().getClassLoader());
				if (resourceBundle == null) {
					LOGGER.warning("Error loading object package " + objectPackage + ": object resource bundle " + objectAnnotation.resourceBundle() + " does not exist");
					return;
				}
				constructors.put(object.getClass().getName(), constructor);
				icons.put(object.getClass().getName(), icon);
				resourceBundles.put(object.getClass().getName(), resourceBundle);
				if (object instanceof Building)
					buildings.add(object.getClass().getName());
				else if (object instanceof MobileUnit)
					mobileUnits.add(object.getClass().getName());
				LOGGER.info("Loaded object package " + objectPackage);
			} catch (final IOException e) {
				LOGGER.log(Level.WARNING, "Error loading object package " + objectPackage, e);
			} catch (final NoSuchMethodException e) {
				LOGGER.log(Level.WARNING, "Error loading object package " + objectPackage, e);
			}
		}
	}
	
	public Object newObject(final String clazz, final UUID id, final Vector position, final Vector orientation) {
		try {
			final Constructor<? extends Object> constructor = constructors.get(clazz);
			if (constructor == null) {
				LOGGER.log(Level.WARNING, "Error creating object of class " + clazz + ": class is not loaded");
				return null;
			}
			return constructor.newInstance(id, position, orientation);
		} catch (final IllegalAccessException e) {
			LOGGER.log(Level.WARNING, "Error creating object of class " + clazz, e);
			return null;
		} catch (final InstantiationException e) {
			LOGGER.log(Level.WARNING, "Error creating object of class " + clazz, e);
			return null;
		} catch (final InvocationTargetException e) {
			LOGGER.log(Level.WARNING, "Error creating object of class " + clazz, e);
			return null;
		}
	}
	
	public Unit newUnit(final String clazz, final UUID id, final Vector position, final Vector orientation) {
		try {
			final Constructor<? extends Object> constructor = constructors.get(clazz);
			if (constructor == null) {
				LOGGER.log(Level.WARNING, "Error creating unit of class " + clazz + ": class is not loaded");
				return null;
			}
			if ((!buildings.contains(clazz)) && (!mobileUnits.contains(clazz))) {
				LOGGER.log(Level.WARNING, "Error creating unit of class " + clazz + ": class is not a unit class");
			}
			return (Unit) constructor.newInstance(id, position, orientation);
		} catch (final IllegalAccessException e) {
			LOGGER.log(Level.WARNING, "Error creating unit of class " + clazz, e);
			return null;
		} catch (final InstantiationException e) {
			LOGGER.log(Level.WARNING, "Error creating unit of class " + clazz, e);
			return null;
		} catch (final InvocationTargetException e) {
			LOGGER.log(Level.WARNING, "Error creating unit of class " + clazz, e);
			return null;
		}		
	}
	
	public Icon getIcon(final String clazz) {
		return icons.get(clazz);
	}
	
	public String getDescription(final String clazz) {
		return resourceBundles.get(clazz).getString("_DESCRIPTION");
	}
	
	public Set<JButton> getActions(final String clazz) {
		return actions.get(clazz);
	}
	
	public Set<String> getBuildings() {
		return buildings;
	}
	
	public Set<String> getMobileUnits() {
		return mobileUnits;
	}
}
